SPB Git
2.9 KB · 22 lines tsx
1import type { Metadata } from "next";2import Link from "next/link";3import { getLocale } from "@/lib/i18n/server";4import { href, t } from "@/lib/i18n";5import { logSearch, search, type SearchMode } from "@/lib/papers/search";6export const dynamic = "force-dynamic";7export async function generateMetadata(): Promise<Metadata> { const d = t(await getLocale()); return { title: d.search.title, description: d.search.lead, alternates: { canonical: "/search" } }; }8export default async function SearchPage({ searchParams }: { searchParams: Promise<{ q?: string; mode?: string }> }) {9  const sp = await searchParams; const locale = await getLocale(); const d = t(locale);10  const q = (sp.q ?? "").trim(); const mode = (["hybrid", "lexical", "semantic"].includes(sp.mode ?? "") ? sp.mode : "hybrid") as SearchMode;11  const res = q ? await search(q, mode, 25) : null; if (res) await logSearch(q, res.mode, res.hits.length, locale, res.tookMs);12  return (<div className="container py-10 max-w-4xl"><h1 className="text-3xl sm:text-4xl">{d.search.title}</h1><p className="meta mt-2">{d.search.lead}</p>13    <form method="get" action={href(locale, "/search")} role="search" className="mt-6 card p-4 grid gap-3 sm:grid-cols-[1fr_auto_auto]">14      <label htmlFor="q" className="sr-only">{d.nav.search}</label><input id="q" name="q" defaultValue={q} className="input" placeholder={d.home.searchPlaceholder} autoFocus />15      <select name="mode" defaultValue={mode} aria-label={d.search.mode} className="input !w-auto"><option value="hybrid">{d.search.hybrid}</option><option value="lexical">{d.search.lexical}</option><option value="semantic">{d.search.semantic}</option></select>16      <button type="submit" className="btn btn-primary">{d.search.go}</button></form>17    {res && (<section className="mt-6"><p className="meta mb-3">{res.hits.length} {res.hits.length === 1 ? d.papers.result : d.papers.results} · {d.search.took} {res.tookMs} ms · {res.semanticUsed ? d.search.semantic : d.search.lexical}{res.mode === "hybrid" && res.semanticUsed ? ` + ${d.search.lexical}` : ""}</p>18      {res.hits.length === 0 && <p className="card p-8 text-center text-fg-2">{d.search.none}</p>}19      <ol className="space-y-3">{res.hits.map((h) => <li key={h.slug} className="card p-4"><p className="text-xs flex flex-wrap gap-2 items-center"><span className="font-mono text-navy">{h.identifier}</span><span className="text-fg-3">{h.year ?? ""}</span>{h.signals.map((s) => <span key={s} className="chip">{d.search.fieldHit} {s}</span>)}</p><h2 className="text-lg mt-1"><Link href={href(locale, `/papers/${h.slug}`)} className="text-fg no-underline hover:underline">{h.title}</Link></h2><p className="meta">{h.authors.join(", ")}</p>{h.snippets.map((s, i) => <p key={i} className="text-sm text-fg-2 mt-2 leading-relaxed"><span dangerouslySetInnerHTML={{ __html: s.text.replace(/<(?!\/?mark>)[^>]*>/g, "") }} />{s.page ? <span className="text-fg-3"> — p. {s.page}</span> : null}</p>)}</li>)}</ol></section>)}20  </div>);21}22
Files · ef2aafa50a0de3a6025a4230d5261ba6d89d122c
Menu

Theme